/* *
 *
 *  Copyright (c) 2019-2019 Highsoft AS
 *
 *  Boost module: stripped-down renderer for higher performance
 *
 *  License: highcharts.com/license
 *
 *  This files contains generic utility functions used by the boost module.
 *
 *  !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
 *
 * */
"use strict";
import H from "../../parts/Globals.js";
import "../../parts/Series.js";
import boostableMap from "./boostable-map.js";
import createAndAttachRenderer from "./boost-attach.js";
var win = H.win,
  doc = win.document,
  pick = H.pick;
// This should be a const.
var CHUNK_SIZE = 3000;
/**
 * Tolerant max() function.
 *
 * @private
 * @function patientMax
 *
 * @param {...Array<Array<unknown>>} args
 * Max arguments
 *
 * @return {number}
 * Max value
 */
function patientMax() {
  var args = [];
  for (var _i = 0; _i < arguments.length; _i++) {
    args[_i] = arguments[_i];
  }
  var r = -Number.MAX_VALUE;
  args.forEach(function (t) {
    if (
      typeof t !== "undefined" &&
      t !== null &&
      typeof t.length !== "undefined"
    ) {
      // r = r < t.length ? t.length : r;
      if (t.length > 0) {
        r = t.length;
        return true;
      }
    }
  });
  return r;
}
/**
 * Return true if ths boost.enabled option is true
 *
 * @private
 * @function boostEnabled
 *
 * @param {Highcharts.Chart} chart
 * The chart
 *
 * @return {boolean}
 * True, if boost is enabled.
 */
function boostEnabled(chart) {
  return pick(
    chart &&
      chart.options &&
      chart.options.boost &&
      chart.options.boost.enabled,
    true
  );
}
/**
 * Returns true if we should force boosting the chart
 * @private
 * @function shouldForceChartSeriesBoosting
 *
 * @param {Highcharts.Chart} chart
 * The chart to check for forcing on
 *
 * @return {boolean}
 * True, if boosting should be forced.
 */
function shouldForceChartSeriesBoosting(chart) {
  // If there are more than five series currently boosting,
  // we should boost the whole chart to avoid running out of webgl contexts.
  var sboostCount = 0,
    canBoostCount = 0,
    allowBoostForce = pick(
      chart.options.boost && chart.options.boost.allowForce,
      true
    ),
    series;
  if (typeof chart.boostForceChartBoost !== "undefined") {
    return chart.boostForceChartBoost;
  }
  if (chart.series.length > 1) {
    for (var i = 0; i < chart.series.length; i++) {
      series = chart.series[i];
      // Don't count series with boostThreshold set to 0
      // See #8950
      // Also don't count if the series is hidden.
      // See #9046
      if (series.options.boostThreshold === 0 || series.visible === false) {
        continue;
      }
      // Don't count heatmap series as they are handled differently.
      // In the future we should make the heatmap/treemap path compatible
      // with forcing. See #9636.
      if (series.type === "heatmap") {
        continue;
      }
      if (boostableMap[series.type]) {
        ++canBoostCount;
      }
      if (
        patientMax(
          series.processedXData,
          series.options.data,
          // series.xData,
          series.points
        ) >= (series.options.boostThreshold || Number.MAX_VALUE)
      ) {
        ++sboostCount;
      }
    }
  }
  chart.boostForceChartBoost =
    allowBoostForce &&
    ((canBoostCount === chart.series.length && sboostCount > 0) ||
      sboostCount > 5);
  return chart.boostForceChartBoost;
}
/* eslint-disable valid-jsdoc */
/**
 * Performs the actual render if the renderer is
 * attached to the series.
 * @private
 * @param renderer {OGLRenderer} - the renderer
 * @param series {Highcharts.Series} - the series
 */
function renderIfNotSeriesBoosting(renderer, series, chart) {
  if (
    renderer &&
    series.renderTarget &&
    series.canvas &&
    !(chart || series.chart).isChartSeriesBoosting()
  ) {
    renderer.render(chart || series.chart);
  }
}
/**
 * @private
 */
function allocateIfNotSeriesBoosting(renderer, series) {
  if (
    renderer &&
    series.renderTarget &&
    series.canvas &&
    !series.chart.isChartSeriesBoosting()
  ) {
    renderer.allocateBufferForSingleSeries(series);
  }
}
/**
 * An "async" foreach loop. Uses a setTimeout to keep the loop from blocking the
 * UI thread.
 *
 * @private
 *
 * @param arr {Array} - the array to loop through
 * @param fn {Function} - the callback to call for each item
 * @param finalFunc {Function} - the callback to call when done
 * @param chunkSize {Number} - the number of iterations per timeout
 * @param i {Number} - the current index
 * @param noTimeout {Boolean} - set to true to skip timeouts
 */
function eachAsync(arr, fn, finalFunc, chunkSize, i, noTimeout) {
  i = i || 0;
  chunkSize = chunkSize || CHUNK_SIZE;
  var threshold = i + chunkSize,
    proceed = true;
  while (proceed && i < threshold && i < arr.length) {
    proceed = fn(arr[i], i);
    ++i;
  }
  if (proceed) {
    if (i < arr.length) {
      if (noTimeout) {
        eachAsync(arr, fn, finalFunc, chunkSize, i, noTimeout);
      } else if (win.requestAnimationFrame) {
        // If available, do requestAnimationFrame - shaves off a few ms
        win.requestAnimationFrame(function () {
          eachAsync(arr, fn, finalFunc, chunkSize, i);
        });
      } else {
        setTimeout(function () {
          eachAsync(arr, fn, finalFunc, chunkSize, i);
        });
      }
    } else if (finalFunc) {
      finalFunc();
    }
  }
}
/**
 * Returns true if the current browser supports webgl
 *
 * @private
 * @function hasWebGLSupport
 *
 * @return {boolean}
 */
function hasWebGLSupport() {
  var i = 0,
    canvas,
    contexts = ["webgl", "experimental-webgl", "moz-webgl", "webkit-3d"],
    context = false;
  if (typeof win.WebGLRenderingContext !== "undefined") {
    canvas = doc.createElement("canvas");
    for (; i < contexts.length; i++) {
      try {
        context = canvas.getContext(contexts[i]);
        if (typeof context !== "undefined" && context !== null) {
          return true;
        }
      } catch (e) {
        // silent error
      }
    }
  }
  return false;
}
/* eslint-disable no-invalid-this */
/**
 * Used for treemap|heatmap.drawPoints
 *
 * @private
 * @function pointDrawHandler
 *
 * @param {Function} proceed
 *
 * @return {*}
 */
function pointDrawHandler(proceed) {
  var enabled = true,
    renderer;
  if (this.chart.options && this.chart.options.boost) {
    enabled =
      typeof this.chart.options.boost.enabled === "undefined"
        ? true
        : this.chart.options.boost.enabled;
  }
  if (!enabled || !this.isSeriesBoosting) {
    return proceed.call(this);
  }
  this.chart.isBoosting = true;
  // Make sure we have a valid OGL context
  renderer = createAndAttachRenderer(this.chart, this);
  if (renderer) {
    allocateIfNotSeriesBoosting(renderer, this);
    renderer.pushSeries(this);
  }
  renderIfNotSeriesBoosting(renderer, this);
}
/* eslint-enable no-invalid-this, valid-jsdoc */
var funs = {
  patientMax: patientMax,
  boostEnabled: boostEnabled,
  shouldForceChartSeriesBoosting: shouldForceChartSeriesBoosting,
  renderIfNotSeriesBoosting: renderIfNotSeriesBoosting,
  allocateIfNotSeriesBoosting: allocateIfNotSeriesBoosting,
  eachAsync: eachAsync,
  hasWebGLSupport: hasWebGLSupport,
  pointDrawHandler: pointDrawHandler,
};
// This needs to be fixed.
H.hasWebGLSupport = hasWebGLSupport;
export default funs;
